package com.xz.coursearrange.GA.impl;

import com.xz.coursearrange.common.ConstantInfo;
import com.xz.coursearrange.entity.ClassTask;
import org.apache.commons.lang3.ArrayUtils;

import java.util.*;

/**
 * @Description
 */
public class GAUtils {
    // 硬条件冲突的惩罚权重
    private static final int TEACHER_CONFLICT_PENALTY = 10000;
    private static final int STUDENT_CONFLICT_PENALTY = 10000;
    private static final int CLASSROOM_CONFLICT_PENALTY = 1000;

    /**
     * 课程表最大节数，当前是 40
     */
    public static final int MAX_CLASS_TIME = 40;


    /**
     * 判断一个字符串是否全部由数字组成
     *
     * @param str
     * @return
     */
    public boolean isNumeric(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        return str.chars().allMatch(Character::isDigit);
    }

    public static String formatCourseInfo(ClassTask classTask){
        return classTask.getIsFix() + classTask.getGradeNo() + classTask.getClassNo()
                + classTask.getTeacherNo() + classTask.getCourseNo() + classTask.getCourseAttr();
    }

    public static int[] getFixTime(String str){
        int[] intArray = new int[str.length() / 2];
        for (int i = 0; i < str.length(); i += 2) {
            intArray[i / 2] = Integer.parseInt(str.substring(i, i + 2)) - 1;
        }
        return intArray;
    }


    /**
     * 用于切割获得编码的染色体中需要的属性
     *
     * @param aim 目标信息
     * @param source 源编码
     * @return 切割出来的目标信息
     */
    public static String cutGene(String aim, String source) {
        switch (aim) {
            case ConstantInfo.IS_FIX:
                // 固定时间 1
                return source.substring(0, 1);
            case ConstantInfo.GRADE_NO:
                // 年级编号 2
                return source.substring(1, 3);
            case ConstantInfo.CLASS_NO:
                // 班级编号 11
                return source.substring(3, 11);
            case ConstantInfo.TEACHER_NO:
                // 讲师编号 5
                return source.substring(11, 16);
            case ConstantInfo.COURSE_NO:
                // 课程编号 6
                return source.substring(16, 22);
            case ConstantInfo.COURSE_ATTR:
                // 课程属性 2
                return source.substring(22, 24);
            default:
                return "";
        }
    }

    /**
     * @description int -> String 格式化时间
     * @return
     **/
    private static String getTimeFormat(int temp) {
        return temp < 10 ? ("0" + temp) : String.valueOf(temp);
    }

    /**
     * 硬条件 总体计算
     */
    public static double calculateHardValue(Individual individual){
        double fitness = 0;

        // 硬条件冲突惩罚
        /**
         * 老师冲突（一个老师同一时间教多门课）→ 重罚
         * 学生冲突（一个班同一时间被排两门课）→ 重罚
         */
        fitness -= checkTeacherConflicts(individual) * TEACHER_CONFLICT_PENALTY;
        fitness -= checkStudentConflicts(individual) * STUDENT_CONFLICT_PENALTY;

        return fitness;
    }

    /**
     * 硬条件
     * 教师冲突
     */
    private static int checkTeacherConflicts(Individual individual) {
        int conflicts = 0;
        Map<String, Set<Integer>> teacherSchedule = new HashMap<>();

        Map<String, String[]> chromosome = individual.getChromosome();
        if (chromosome != null) {
            for (Map.Entry<String, String[]> entry : chromosome.entrySet()) {
                String classNo = entry.getKey();
                String[] periods = entry.getValue();

                if (periods != null) {
                    for (int i = 0; i < periods.length; i++) {
                        String teacherNo = periods[i];
                        if (teacherNo != null && !teacherNo.isEmpty()) {
                            teacherSchedule.computeIfAbsent(teacherNo, k -> new HashSet<>());
                            //如果一个老师同一时间段出现多次 → 冲突
                            if (!teacherSchedule.get(teacherNo).add(i)) {
                                conflicts++;
                            }
                        }
                    }
                }
            }
        }

        return conflicts;
    }

    /**
     * 硬条件  `
     * 学生冲突
     */
    private static int checkStudentConflicts(Individual individual) {
        int conflicts = 0;
        Map<String, String[]> chromosome = individual.getChromosome();

        if (chromosome != null) {
            for (String classNo : chromosome.keySet()) {
                Set<Integer> schedule = new HashSet<>();
                String[] periods = chromosome.get(classNo);

                if (periods != null) {
                    for (int i = 0; i < periods.length; i++) {
                        //如果同一时间段有多个课程记录 → 冲突
                        if (schedule.contains(i)) {
                            conflicts++;
                        } else {
                            schedule.add(i);
                        }
                    }
                }
            }
        }

        return conflicts;
    }

    /**
     * 软条件 总体计算
     */
    public static double calculateSoftValue(Individual individual){
        double fitness = 0;

        fitness += calculateCourseValue(individual);
        fitness += checkTeacherTimeSoftConstraints(individual);

        return fitness;
    }

    /**
     * 软条件 (课程)
     * 计算
     */
    private static double calculateCourseValue(Individual individual) {
        // <01 20200101 10010 100001 01 05> 优先 01 =》 5   05 =》 1
        // <01 20200101 10010 100001 03 00> 次
        /**
         * 课程类型	权重 K	时间偏好
         * 主课	    0.3	    尽量上午
         * 次要课	0.1	    尽量中间时段
         * 体育课	0.1	    尽量排在下午
         * 实验课	0.3	    排在特定节次
         * 离散度	0.2	    课尽量分布均匀
         */
        // 主要课所占权重 01
        double K1 = 0.3;
        // 次要课所占权重 02
        double K2 = 0.1;
        // 体育课所占权重 03
        double K3 = 0.1;
        // 实验课所占权重 03
        double K4 = 0.3;

        // 课程离散程度所占权重
        double K5 = 0.2;

        int F1 = 0; // 主要课程期望总值
        int F2 = 0; // 次要课程期望总值
        int F3 = 0; // 体育课期望总值
        int F4 = 0; // 实验课期望总值
        int F5; // 课程离散程度期望总值

        double Fx = 0.0; // 总适应度值

        Map<String, String[]> chromosome = individual.getChromosome();
        if (chromosome != null) {
            for (String classNo : chromosome.keySet()) {
                String[] periods = chromosome.get(classNo);
                if (periods != null) {
                    // i 就是该课程的开课时间
                    for (int i = 0; i < periods.length; i++) {
                        // 获得课程属性
                        String courseAttr = individual.getCourseAttr(classNo, i);
                        // 获得该课程的开课时间
                        String classTime = getTimeFormat((i + 1));
                        if (courseAttr != null && !courseAttr.isEmpty()) {
                            switch (courseAttr) {
                                case ConstantInfo.MAIN_COURSE:
                                    // 在该时间段 主要课程 开课的期望 calculateMainExpect(classTime)
                                    F1 = F1 + calculateMainExpect(classTime);
                                    break;
                                case ConstantInfo.SECONDARY_COURSE: // 次要课程
                                    F2 = F2 + calculateSecondaryExpect(classTime);
                                    break;
                                case ConstantInfo.PHYSICAL_COURSE: // 体育课
                                    F3 = F3 + calculatePhysicalExpect(classTime);
                                    break;
                                case ConstantInfo.EXPERIMENT_COURSE:
                                    F4 = F4 + calculateExperimentExpect(classTime);
                                    break;
                            }
                        }
                    }
                }
            }

            // 计算期望值
            F5 = calculateDiscreteExpect(individual);
            // 总适应度 整个种群的适应度值
            Fx += K1 * F1 + K2 * F2 + K3 * F3 + K4 * F4 + K5 * F5;
        }

        return Fx;
    }

    /**
     * 软条件（教师）
     * 计算
     */
    // 检测教师上课时间软条件
    private static double checkTeacherTimeSoftConstraints(Individual individual) {
        int penalty = 0;
        Map<String, List<Integer>> teacherTimeSlots = new HashMap<>();

        Map<String, String[]> chromosome = individual.getChromosome();

        for (String classNo : chromosome.keySet()) {
            String[] schedule = chromosome.get(classNo);

            for (int i = 0; i < schedule.length; i++) {
                String teacherNo = individual.getTeacherNo(classNo, i);

                if (!teacherTimeSlots.containsKey(teacherNo)) {
                    teacherTimeSlots.put(teacherNo, new ArrayList<>());
                }
                teacherTimeSlots.get(teacherNo).add(i);
            }
        }

        // 计算教师课表的软条件
        for (String teacherNo : teacherTimeSlots.keySet()) {
            List<Integer> timeSlots = teacherTimeSlots.get(teacherNo);
            Collections.sort(timeSlots);

            for (int i = 0; i < timeSlots.size() - 1; i++) {
                int timeDiff = timeSlots.get(i + 1) - timeSlots.get(i);
                // 评分
                if (timeDiff == 1){
                    penalty -= 2; // 连续上两节课扣分
                } else
                if (timeDiff == 0) {
                    penalty -= 1000;  // 同一时间段重复课的重罚
                } else
                    //分布适中 → 加分
                if (timeDiff >= 4 && timeDiff <= 7){
                    penalty += 10;
                } else
                if (timeDiff >= 7 && timeDiff <= 11){
                    penalty += 3;
                } else
                if (timeDiff <= 4 && timeDiff >= 1){
                    penalty += 5;
                }else if (timeDiff >= 30 && timeDiff <= 40){
                    penalty -= 1;
                }
            }
        }

        return penalty;
    }


    /**
     * 计算主要课程的期望值
     * 例如语文数学英语在高中阶段是需要设置多一点，设置在前面上课
     */
    private static int calculateMainExpect(String classTime) {
        // 主要课程期望值为10时的时间片值，放在第一节课
        String[] tenExpectValue = {"01", "02", "9", "10", "17", "18", "25", "26", "33", "34"};
        // 主要课程期望值为8时的时间片值
        String[] eightExpectValue = {"03", "04", "11", "12", "19", "20", "27", "28", "35", "36"};
        // 主要课程期望值为4时的时间片值
        String[] fourExpectValue = {"05","13", "21", "29", "37"};
        // 主要课程期望值为2时的时间片值
        String[] twoExpectValue = {"06", "14", "22", "30", "38"};

        if (ArrayUtils.contains(tenExpectValue, classTime)) {
            return 10;
        } else if (ArrayUtils.contains(eightExpectValue, classTime)) {
            return 10;
        } else if (ArrayUtils.contains(fourExpectValue, classTime)) {
            return 4;
        } else if (ArrayUtils.contains(twoExpectValue, classTime)) {
            return 2;
        } else {
            return 0;
        }
    }

    /**
     * 计算次要课程的期望值
     * 物理、化学、生物
     * 政治、历史、地理
     */
    private static int calculateSecondaryExpect(String classTime) {
        // 次要课程期望值为10时的时间片值
        String[] tenExpectValue = {"03", "04", "11", "12", "19", "20", "27", "28", "35", "36"};
        // 次要课程期望值为8时的时间片值
        String[] eightExpectValue = {"05", "06", "13", "14", "21", "22", "29", "30", "37", "38"};
        // 次要课程期望值为4时的时间片值
        String[] fourExpectValue = {"07", "08", "15", "16", "23", "24", "31", "32", "39", "40"};
        //String [] zeroExpectValue = {"05","10","15","20","25"};//选修课期望值为0时的时间片值

        if (ArrayUtils.contains(tenExpectValue, classTime)) {
            return 10;
        } else if (ArrayUtils.contains(eightExpectValue, classTime)) {
            return 8;
        } else if (ArrayUtils.contains(fourExpectValue, classTime)) {
            return 4;
        } else {
            return 0;
        }
    }

    /**
     * 计算体育课的期望值
     *
     * @param classTime 上课时间
     * @return 期望值
     */
    private static int calculatePhysicalExpect(String classTime) {
        String[] tenExpectValue = {"08", "16", "24", "32", "40"};//体育课期望值为10时的时间片值  24
        String[] eightExpectValue = {"07", "15", "23", "31", "39"};//体育课期望值为8时的时间片值 23
        String[] fourExpectValue = {"05", "06", "13", "14", "21", "22", "29", "30", "37", "38"};//体育课期望值为4时的时间片值
        //String [] zeroExpectValue = {"01","05","06","10","11","15","16","20","21","23","24","25"};//体育课期望值为0时的时间片值

        if (ArrayUtils.contains(tenExpectValue, classTime)) {
            return 10;
        } else if (ArrayUtils.contains(eightExpectValue, classTime)) {
            return 8;
        } else if (ArrayUtils.contains(fourExpectValue, classTime)) {
            return 4;
        } else {
            return 0;
        }
    }


    /**
     * 计算实验课的期望值
     */
    private static int calculateExperimentExpect(String classTime) {
        String[] tenExpectValue = {"05", "13", "21", "29", "37"};//实验课期望值为10时的时间片值
        String[] eightExpectValue = {"04", "12", "20", "28", "36"};//实验课期望值为8时的时间片值 {"04", "12", "20", "28", "36"}
        String[] sixExpectValue = {"08", "16", "24", "32", "40"};//实验课期望值为6时的时间片值
        String[] fourExpectValue = {"06", "14", "22", "30", "38"};//实验课期望值为4时的时间片值 "06", "14", "22", "30", "38"
        //String [] zeroExpectValue = {"01","06","11","16","21","23","24","25"};//实验课期望值为0时的时间片值

        if (ArrayUtils.contains(tenExpectValue, classTime)) {
            return 10;
        } else if (ArrayUtils.contains(eightExpectValue, classTime)) {
            return 8;
        } else if (ArrayUtils.contains(sixExpectValue, classTime)) {
            return 6;
        } else if (ArrayUtils.contains(fourExpectValue, classTime)) {
            return 4;
        } else {
            return 0;
        }
    }


    /**
     * 计算课程离散度期望值
     * 指的是：同一门课程在一周中是否排得太分散。
     * 举个例子：
     *  理想情况（连续排课）：
     *      数学课：周一第2节、周二第3节、周三第1节 → 比较集中，学生容易形成连贯知识
     *  差的情况（离散严重）：
     *      数学课：周一第1节、周四第7节、周五第8节 → 太分散，学习效率低
     */
    private static int calculateDiscreteExpect(Individual individual) {
        // 离散程度期望值
        int F5 = 0;
        // 返回每个班级的对应课程下面的排序上课时间
        Map<String, Map<String, List<Integer>>> classTimeMap = courseGrouping(individual);

        for (String classNo : classTimeMap.keySet()) {
            for (List<Integer> classTimeList : classTimeMap.get(classNo).values()) {
                if (classTimeList.size() > 1) {
                    for (int i = 0; i < classTimeList.size() - 1; ++i) {
                        // 计算一门课上课的时间差
                        // 上一门上课的时间 到 下一门课的时间  差
                        // 越分散值越低
                        int temp = classTimeList.get(i+1) - classTimeList.get(i);
                        F5 += judgingDiscreteValues(temp);
                    }
                }
            }
        }

        return F5;
    }

    /**
     * 将一个个体（多个班级课表）的同一门课程的所有上课时间进行统计，并且进行分组
     * @return 班级  课程  时间段列表
     */
    private static Map<String, Map<String, List<Integer>>> courseGrouping(Individual individual) {
        Map<String, Map<String, List<Integer>>> classTimeMap = new HashMap<>();
        Map<String, String[]> chromosome = individual.getChromosome();

        if (chromosome != null) {
            for (Map.Entry<String, String[]> entry : chromosome.entrySet()) {
                String classNo = entry.getKey();
                String[] periods = entry.getValue();

                if (periods != null) {
                    classTimeMap.computeIfAbsent(classNo, k -> new HashMap<>());
                    for (int i = 0; i < periods.length; i++) {
                        String courseNo = individual.getCourseNo(classNo, i);
                        if (courseNo != null && !courseNo.isEmpty()) {
                            classTimeMap.get(classNo).computeIfAbsent(courseNo, k -> new ArrayList<>()).add(i);
                        }
                    }
                }
            }
        }

        return classTimeMap;
    }

    /**
     * 判断两课程的时间差在哪个区间
     * 并返回对应的期望值
     */
    private static int judgingDiscreteValues(int temp) {
        int[] tenExpectValue = {5, 6, 7, 8}; // 期望值为10时两课之间的时间差   合适区间
        int[] sixExpectValue = {4, 9, 10, 11, 12, 13}; // 期望值为6时两课之间的时间差
        int[] fourExpectValue = {3, 14, 15, 16, 17, 18}; // 期望值为4时两课之间的时间差
        int[] twoExpectValue = {2, 19, 20, 21, 22, 23}; // 期望值为2时两课之间的时间差   时间差距很大 或 紧密链接
        //int [] zeroExpectValue = {1,24};//期望值为0时两课之间的时间差
        if (ArrayUtils.contains(tenExpectValue, temp)) {
            return 10;
        } else if (ArrayUtils.contains(sixExpectValue, temp)) {
            return 6;
        } else if (ArrayUtils.contains(fourExpectValue, temp)) {
            return 4;
        } else if (ArrayUtils.contains(twoExpectValue, temp)) {
            return 2;
        } else {
            return 0;
        }
    }
}
